/*
This file is part of asproxy.

asproxy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

asproxy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with asproxy.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "proxythread.h"
#include "proxyhelper.h"
#include "log.h"

void* processclient(void* arg)
{
	// increment the number of worker threads
	incrementworkercount();
	
	char *buffer = (char *)malloc(g_proxyconfig.BufferSize);
	int clientfd = 0;
	int serverfd = 0;
	SSL_CTX* ctx = NULL;	
	SSL* ssl = NULL;
	fd_set read_fds;
	
	thread_parm_t *parm = (thread_parm_t *)arg;
	
	if (parm != NULL)
	{
		clientfd = parm->value;
		log_info(parm->string);
		free(parm);
	}
	
	if (clientfd <= 0)
		goto END_PROCESS;
		
	// clear errno
	errno = 0;
	
	// Setup SSL if required
	if (strcmp(g_proxyconfig.SSLCertFile, NONE_DEFAULT) != 0)
	{
		ERR_clear_error();
		ctx = initserverctx();
		if (ctx == NULL)
			goto END_PROCESS;	
		
		ssl = setupssl(clientfd, ctx);
		if (ssl == NULL)
			goto END_PROCESS;		
		SSL_set_fd(ssl, clientfd);
		
		// Complete the handshake
		int nacc = 0;
		nacc = SSL_accept(ssl);
		if (nacc <= 0)
		{
			logsslerror("SSL_accept", ssl, nacc);
			goto END_PROCESS;
		}
	}

	// Connect to the host server
	serverfd = connectserver();
	if (serverfd <= 0)
		goto END_PROCESS;		
	
	if (strcmp(g_proxyconfig.SourceDetailFormat, NONE_DEFAULT) != 0)
		sendsourceinfo(clientfd, serverfd);
		
	while (1)
	{
		// Initialize the buffer
		bzero(buffer, g_proxyconfig.BufferSize);
	
		/* Set up polling using select. */
		FD_ZERO(&read_fds);

		// Set the readers
		FD_SET(clientfd, &read_fds);
		FD_SET(serverfd, &read_fds);
		int maxfd = (clientfd > serverfd ? clientfd : serverfd) + 1;
		
		struct timeval socktimeout;
		socktimeout.tv_sec = g_proxyconfig.SocketTimeout;
		socktimeout.tv_usec = 0;
					
		// Continue with the operation
		int nready = select(maxfd, &read_fds, (fd_set *)0, (fd_set *)0, &socktimeout);		
		if (nready <= 0) // Timeout
		{
			log_error("Socket Timeout.");
			goto END_PROCESS;
		}

		if (FD_ISSET(serverfd, &read_fds))
		{				
			int serverbytes = 0;
			if ((serverbytes = read(serverfd, buffer, g_proxyconfig.BufferSize - 1)) > 0)
			{
				// Update the bytes and length if neccessary before sending to the server
				if (g_phandle_outgoing != NULL)
					buffer = (*g_phandle_outgoing)(buffer, &serverbytes);
					
				int clientbytes = 0;
				
				if (ssl == NULL)
					clientbytes = write(clientfd, buffer, serverbytes);
				else 
					clientbytes = SSL_write(ssl, buffer, serverbytes);
				
				if (clientbytes < 0)
				{
					if (ssl != NULL)
						logsslerror("SSL_write", ssl, clientbytes);
					else if (errno > 0)
						log_error(strerror(errno));
						
					goto END_PROCESS;
				}
				else if (clientbytes == 0)
					goto END_PROCESS;

				// Log the details to SysLog
				log_transport("RES", buffer);
				bzero(buffer, g_proxyconfig.BufferSize);				
			}
			else 
			{
				if (errno > 0)
					log_error(strerror(errno));
				goto END_PROCESS;
			}
		}			
		else if (FD_ISSET(clientfd, &read_fds))
		{					
			int clientbytes = 0;
	
			if (ssl == NULL)
				clientbytes = read(clientfd, buffer, g_proxyconfig.BufferSize - 1);
			else
				clientbytes = SSL_read(ssl, buffer, g_proxyconfig.BufferSize - 1);
			
			if (clientbytes > 0)
			{
				// Update the bytes and length if neccessary before sending to the server
				if (g_phandle_incoming != NULL)
					buffer = (*g_phandle_incoming)(buffer, &clientbytes);
				
				int serverbytes = 0;
				serverbytes = write(serverfd, buffer, clientbytes);				
				if (serverbytes < 0)
				{
					if (errno > 0)
						log_error(strerror(errno));
					goto END_PROCESS;
				}
				else if (serverbytes == 0)
					goto END_PROCESS;

				// Log the details to SysLog
				log_transport("REQ", buffer);
				bzero(buffer, g_proxyconfig.BufferSize);		
			}
			else 
			{
				if ((ssl != NULL) && (clientbytes < 0))
				{
					int sslerr = SSL_get_error(ssl, clientbytes);

					if ((sslerr == SSL_ERROR_WANT_READ) || (sslerr == SSL_ERROR_WANT_WRITE))
						continue;						
					else 
						logsslerror("SSL_read", ssl, clientbytes);					
				}
				else if ((clientbytes < 0) && (errno > 0)) 	
					log_error(strerror(errno));
					
				goto END_PROCESS;
			}
		}
	}

END_PROCESS:		
	log_info("Terminating client thread.");

	// Free the Buffer
	free(buffer);
	buffer = NULL;

	// Drain the server socket buffer just in case there is some data in there
	if (serverfd > 0)
		drainsocketbuffer(serverfd, NULL);

	// Send a Shutdown to the server if it still exists
	if (serverfd > 0)
		shutdown(serverfd, SHUT_RDWR);		
	if (serverfd > 0)
		close(serverfd);
		
	log_info("Shutdown server connection.");
	
	// Drain the client socket buffer just in case there is some data in there
	if (clientfd > 0)
		drainsocketbuffer(clientfd, ssl);

	if ((ssl != NULL) && (clientfd > 0))
		SSL_free(ssl);
	
	if (clientfd > 0)
		shutdown(clientfd, SHUT_RDWR);
	if (clientfd > 0)
		close(clientfd);
	
	ERR_remove_state(0);
	
	// decrement the number of worker threads
	decrementworkercount();
	log_info("Terminated client thread.");
}





